package graphicView;

import java.io.File;
import java.io.FileWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Vector;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.RepaintManager;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;

import model.ClassImpl;
import model.ClassEditor;
import model.DiagramImpl;
import model.PackageEditor;
import model.ParameterImpl;
import model.RelationshipImpl;
import model.PackageImpl;
import model.RelationshipEditor;

/**
 * GraphicController is the startup class and controller for the java swing
 * version of word search. It owns the graphical user interface and puzzle.
 * 
 * @author David Hutchens - Modified by Team EBE - Elite Bombard Elephants
 * 
 *         GraphicController.java is modified by Team EBE to handle the
 *         ClassGUI.java and RelationshipGUI.java
 */

// Print based code, augmented from download.oracle.com
// Pageable based code, augmented from docstore.mik.ua
public class GraphicController implements Printable {
	private static GraphicController sharedApp;
	private Component GUI;
	private DiagramUserInterface ui;
	private JFrame theFrame;
	private JFrame newFrame;
	private RelationshipGUI rg;
	private JFrame relFrame;
	private ClassGUI cg;
	private PackageGUI pg;
	private JFrame classFrame;
	private JFrame packageFrame;
	private DiagramImpl theDiagram;
	private ClassEditor theClassEditor;
	private PackageEditor thePackageEditor;
	private ClassGraphicObject cgo;
	private RelationshipEditor theRelEditor;
	private Vector<ClassGraphicObject> classesToDisplay;
	private ActiveClassNotifier activeClassNotifier;
	private RelationshipGraphicObject rgo;
	private Vector<RelationshipGraphicObject> relationshipsToDisplay;
	private Vector<PackageGraphicObject> packagesToDisplay;


	/**
	 * GraphicController constructs the object, creating the model and view
	 * (with the frame and user interface)
	 */
	private GraphicController() {
		// Set the look and feel (for Macs too).
		if (System.getProperty("mrj.version") != null) {
			System.setProperty("apple.laf.useScreenMenuBar", "true");
		}
		JFrame.setDefaultLookAndFeelDecorated(true);

		theFrame = new JFrame("UML");
		theFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		theDiagram = new DiagramImpl();
		theDiagram = buildTestDiagram(theDiagram);

		theClassEditor = new ClassEditor(theDiagram.getClassImpl(0));
		thePackageEditor = new PackageEditor(theDiagram.getPackageImpl(0));

		classFrame = new JFrame("Edit ClassImpl");
		classFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		packageFrame = new JFrame("Edit Package");
		packageFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		cg = new ClassGUI(this, classFrame, theClassEditor);
		pg = new PackageGUI(this, packageFrame, thePackageEditor);

		activeClassNotifier = new ActiveClassNotifier();
		activeClassNotifier.addObserver(theClassEditor);
		activeClassNotifier.addObserver(thePackageEditor);
		activeClassNotifier.addObserver(cg);
		activeClassNotifier.addObserver(pg);
		classesToDisplay = new Vector<ClassGraphicObject>();
		classesToDisplay.add(new ClassGraphicObject(diagram().getClassImpl(0)));
		classesToDisplay.add(new ClassGraphicObject(diagram().getClassImpl(1)));
		relationshipsToDisplay = new Vector<RelationshipGraphicObject>();
		relationshipsToDisplay.add(new RelationshipGraphicObject(diagram()
				.getRelationshipImpl(0)));
		packagesToDisplay = new Vector<PackageGraphicObject>();
		packagesToDisplay.add(new PackageGraphicObject(diagram().getPackageImpl(0)));
		ui = new DiagramUserInterface(this, theFrame, classesToDisplay,
				relationshipsToDisplay, packagesToDisplay, activeClassNotifier);
		theFrame.pack();
		theFrame.setVisible(true);
	}

	/**
	 * printComponent prints the component that is passed in
	 * 
	 * @param c
	 *            the print component that is passed in to be printed
	 */
	public static void printComponent(Component c) {

		new GraphicController(c).print();
	}

	/**
	 * GraphicController sets the component to be printed, which is passed in
	 * 
	 * @param componentToBePrinted
	 *            the component that is to be printed
	 */
	public GraphicController(Component componentToBePrinted) {
		this.GUI = componentToBePrinted;
	}

	/**
	 * print gets the job to be printed, formats the page, then prints if able
	 * to print or throws an error message if not able to print
	 */
	public void print() {
		PrinterJob printJob = PrinterJob.getPrinterJob();
		PageFormat format = printJob.pageDialog(printJob.defaultPage());
		printJob.setPrintable(this, format);
		if (printJob.printDialog())
			try {
				printJob.print();
			} catch (PrinterException pe) {
				System.out.println("Error printing: " + pe);
			}
	}

	// Similar to the Graphic Controller constructor (makes a "New" Frame for
	// the UML diagram)

	/**
	 * newDiagram creates a new, blank frame for the UML editor
	 */
	public void newDiagram() {
		if (System.getProperty("mrj.version") != null) {
			System.setProperty("apple.laf.useScreenMenuBar", "true");
		}
		JFrame.setDefaultLookAndFeelDecorated(true);

		newFrame = new JFrame("New UML");
		newFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		theDiagram = new DiagramImpl();

		classFrame = new JFrame("Edit ClassImpl");
		classFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		packageFrame = new JFrame("Edit Package");
		packageFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		cg = new ClassGUI(this, classFrame, theClassEditor);
		pg = new PackageGUI(this, packageFrame, thePackageEditor);

		activeClassNotifier = new ActiveClassNotifier();
		activeClassNotifier.addObserver(theClassEditor);
		activeClassNotifier.addObserver(cg);
		activeClassNotifier.addObserver(pg);

		classesToDisplay = new Vector<ClassGraphicObject>();

		relationshipsToDisplay = new Vector<RelationshipGraphicObject>();
		
		packagesToDisplay = new Vector<PackageGraphicObject>();

		ui = new DiagramUserInterface(this, newFrame, classesToDisplay,
				relationshipsToDisplay, packagesToDisplay, activeClassNotifier);
		
		newFrame.pack();
		newFrame.setVisible(true);
	}

	/**
	 * buildTestDiagram creates a set of classes with various attributes and
	 * operations for the user to view when the UML editor is started; this test
	 * diagram can be edited by the user
	 * 
	 * @param theDiagram
	 *            the diagram to be implemented
	 * @return theDiagram the diagram that is created with the test classes
	 *         filled with the various attributes and operations
	 */
	private DiagramImpl buildTestDiagram(DiagramImpl theDiagram) {
		theDiagram.addPackage(50, 50);
		theDiagram.addClass("Test ClassImpl 1");
		theDiagram.addClass("Test ClassImpl 2");
		theDiagram.getClassImpl(0).addAttribute("attribute 1", "string", '+');
		theDiagram.getClassImpl(0).addAttribute("attribute 2", "char", '-');
		theDiagram.getClassImpl(0).addOperation("operation 1", "void", '#');
		theDiagram.getClassImpl(1).addAttribute("attribute 1", "string", '+');
		theDiagram.getClassImpl(1).addAttribute("attribute 2", "char", '-');
		theDiagram.getClassImpl(1).addOperation("operation 1", "void", '#');
		theDiagram.getClassImpl(1).setoX(300);
		theDiagram.addRelationship(theDiagram.getClassImpl(0).anchorAt(3),
				theDiagram.getClassImpl(1).anchorAt(7));
		return theDiagram;
	}

	/**
	 * addClass adds class objects to the diagram
	 * 
	 * @param oX
	 *            the destination value of the x origin
	 * @param oY
	 *            the destination value of the y origin
	 */
	public void addClass(int oX, int oY) {
		ClassImpl toAdd = new ClassImpl(oX, oY);
		theDiagram.addClass(toAdd);
		classesToDisplay.add(new ClassGraphicObject(toAdd));
	}

	/**
	 * addPackage adds package objects to the diagram
	 * 
	 * @param oX
	 *            the destination value of the x origin
	 * @param oY
	 *            the destination value of the y origin
	 */
	public void addPackage(int oX, int oY) {
		PackageImpl toAdd = new PackageImpl(oX, oY);
		theDiagram.addPackage(toAdd);
		packagesToDisplay.add(new PackageGraphicObject(toAdd));
	}

	/**
	 * addRelationship adds relationship objects to the diagram
	 * 
	 * @param toAdd
	 *            the relationship to be added and implemented
	 */
	public void addRelationship(RelationshipImpl toAdd) {
		theDiagram.addRelationship(toAdd);
		relationshipsToDisplay.add(new RelationshipGraphicObject(toAdd));
	}

	/**
	 * DiagramImpl diagram the diagram to be implemented
	 * 
	 * @return theDiagram the implemented diagram
	 */
	public DiagramImpl diagram() {
		return theDiagram;
	}

	/**
	 * DiagramUserInterface graphicalInterface1 is the interface that is created
	 * for the diagram
	 * 
	 * @return ui the user interface created for the diagram
	 */
	public DiagramUserInterface graphicalInterface() {
		return ui;
	}

	/**
	 * creates (if necessary) and returns the singleton instance
	 * 
	 * @return the singleton shared instance
	 */
	public static GraphicController sharedInstance() {
		if (sharedApp == null) {
			sharedApp = new GraphicController();
		}
		return sharedApp;
	}

	/**
	 * openRelation opens the relationship editor for the user
	 */
	public void openRelation() {

		if (System.getProperty("mrj.version") != null) {
			System.setProperty("apple.laf.useScreenMenuBar", "true");
		}

		JFrame.setDefaultLookAndFeelDecorated(true);

		relFrame = new JFrame("Edit RelationshipImpl");
		rg = new RelationshipGUI(this, relFrame, theRelEditor);

		relFrame.pack();
		relFrame.setVisible(true);
	}

	/**
	 * RelationshipGUI graphicalInterface1 is the interface that is created for
	 * the relationship gui
	 * 
	 * @return rg the relationship gui that is
	 */
	public RelationshipGUI graphicalInterface1() {
		return rg;
	}

	/**
	 * openClass simply opens the class and sets it to visible
	 */
	public void openClass() {

		classFrame.pack();
		classFrame.setVisible(true);
	}

	/**
	 * openPackage simply opens the package and sets it to visible
	 */
	public void openPackage() {
		packageFrame.pack();
		packageFrame.setVisible(true);
	}

	/**
	 * ClassGUI graphicalInterface2 gets the class gui and returns it
	 * 
	 * @return cg the class gui that is returned
	 */
	public ClassGUI graphicalInterface2() {
		return cg;
	}

	/**
	 * PackageGUI graphicalInterface3 gets the package gui and returns it
	 * 
	 * @return pg the package gui that is returned
	 */
	public PackageGUI graphicalInterface3() {
		return pg;
	}

	/**
	 * saveDiagram generates a string and then outputs that string to a file
	 * which is the saved diagram
	 * 
	 * @throws IOException
	 *             if needed, exception is thrown
	 */
	public void saveDiagram() throws IOException {
		System.out.println("1");
		JFileChooser theChooser = new JFileChooser();
		System.out.println("2");
		int success = theChooser.showSaveDialog(null);
		System.out.println("3");
		File theFile;
		System.out.println("4");
		if (success == JFileChooser.APPROVE_OPTION) {
			System.out.println("5");
			theFile = theChooser.getSelectedFile();
			System.out.println("6");
			FileWriter theWriter = new FileWriter(theFile);
			System.out.println(theFile.getName());
			System.out.println("7");
			System.out.println(theDiagram.saveString());
			theWriter.write(theDiagram.saveString());
			System.out.println("8");
			theWriter.close();
		}
	}

	/**
	 * deleteClass passes in a class object and then deletes that class object
	 * 
	 * @param toDelete
	 *            the class object to be deleted
	 */
	public void deleteClass(ClassGraphicObject toDelete) {
		classesToDisplay.remove(toDelete);
		theDiagram.deleteClass(toDelete.getModel());
		for (RelationshipGraphicObject rgo : relationshipsToDisplay) {
			if (rgo.getModel().getSrc().getName() == toDelete.getModel()
					.getName()
					|| rgo.getModel().getDest().getName() == toDelete
							.getModel().getName()) {
				relationshipsToDisplay.remove(rgo);
				theDiagram.deleteRelationship(rgo.getModel());
				break;
			}
		}
	}

	/**
	 * 
	 * @return
	 */
	public String saveStringGenerator() {

		String saveString = new String();

		saveString = theDiagram.saveString();

		return saveString;
	}

	/**
	 * Creates the runnable job that runs the graphical interface
	 * 
	 * @param args
	 *            are not used
	 */
	public static void main(String[] args) {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				sharedInstance();
			}
		});
	}

	@Override
	public int print(Graphics g, PageFormat pf, int arg2)
			throws PrinterException {

		if (arg2 > 0) { /* We have only one page, and 'page' is zero-based */

			return NO_SUCH_PAGE;
		}// else {

		Graphics2D g2d = (Graphics2D) g;

		g2d.translate(pf.getImageableX(), pf.getImageableY());
		g2d.scale(.45, .75);

		g.equals(g2d);

		if (theFrame.isActive()) {
			disableDoubleBuffering(theFrame);
			theFrame.printAll(g);
			enableDoubleBuffering(theFrame);

		} else if (newFrame.isActive()) {
			disableDoubleBuffering(newFrame);
			newFrame.printAll(g);
			enableDoubleBuffering(newFrame);
		}

		return (PAGE_EXISTS);

	}

	public static void disableDoubleBuffering(Component c) {
		RepaintManager currentManager = RepaintManager.currentManager(c);
		currentManager.setDoubleBufferingEnabled(false);
	}

	public static void enableDoubleBuffering(Component c) {
		RepaintManager currentManager = RepaintManager.currentManager(c);
		currentManager.setDoubleBufferingEnabled(true);
	}

}
